/*
 * Copyright (c) 2010, Tangedyn <tangedyn at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.cat;

import java.text.DecimalFormat;
import java.util.Map;

import yawning.mew.ProcEffect;
import yawning.mew.Report;

class CatReport extends Report {
	// Metrics that we want to report back to the user.
	//
	// Notes:
	// Percent values are stored in decimal form. (Multiply by 100)
	//

	//double mDps;				// DPS
	double mDpsBase;			// DPS > 25%
	double mDpsBitW;			// DPS < 25%
	double pFail;				// pMiss + Dodge
	double pWhiteCrit;			// White crit rate
	double pYellowCrit;			// Yellow crit rate (Base, Bite handled elsewhere.)
	double pWhiteCritCap;		// White crit cap
	double mSwingTimer;			// Swing timer
	double mOoCPPM;				// Omen of Clarity PPM
	double mOoCReturn;			// Energy/OoC Proc
	double mEnergyRegenRate;	// Energy regeneration rate

	double mNrSavageRoar;		// Number of Savage Roars
	double mSavageRoarUptime;	// Savage Roar Uptime
	double mSavageRoarCPs;		// Savage Roar CP/cast

	double mNrMangles;			// Number of Mangles
	double mMangleUptime;		// Mangle debuff uptime

	double mNrShreds;			// Number of Shreds

	double mNrRakes;			// Number of Rakes
	double mRakeUptime;			// Rake DoT uptime

	double mNrRips;				// Number of Rips
	double mRipUptime;			// Rip DoT uptime

	double mNrBites;			// Number of Bites
	double mNrBitesBase;		// Number of Bites > 25%
	double mNrBitesPerRip;		// Number of Bites per Rip (> 25%)
	double mNrBitesBitW;		// Number of Bites < 25%
	double pBiteCrit;			// Bite crit

	double mNrTigersFurys;		// Number of Tiger's Fury
	double mTigersFuryUptime;	// Tiger's Fury uptime

	double mNrBerserks;			// Number of Berserks
	double mBerserkUptime;		// Berserk uptime

	double mSegmentTime;		// Segment time (Encounter duration for sim.)

	double mArmorValue;			// Armor value
	double mArmorMitigation;	// Armor mitigation (%)

	double pWhiteDamage;		// White Damage (%)
	double pFurySwipesDamage;	// Fury Swipes Damage (%)
	double pRavageDamage;		// Ravage Damage (%)
	double pMangleDamage;		// Mangle Damage (%)
	double pShredDamage;		// Shred Damage (%)
	double pRakeDamage;			// Rake Damage (%)
	double pRipDamage;			// Rip Damage (%)
	double pBiteDamage;			// Bite Damage (%)
	double pOtherDamage;		// Other Damage (%) (Procs)

	double mAvgWhiteAttack;		// Average white attack
	double mAvgRavageAttack;	// Average Ravage attack
	double mMangleDPE;			// Mangle damage/energy
	double mShredDPE;			// Shred damage/energy
	double mRakeDPE;			// Rake damage/energy
	double mRipDPE;				// Rip damage/energy
	double mBiteDPE;			// Bite damage/energy

	Map<ProcEffect, Double> mProcUptimes;	// Proc uptimes.

	@Override
	public Report accumulate(Report r) {
		super.accumulate(r);

		CatReport cr = (CatReport) r;

		// TODO Figure out what numbers we want to generate errors for. (mDPSBase/mDPSFof probably)
		//mDps += cr.mDps;
		mDpsBase += cr.mDpsBase;
		mDpsBitW += cr.mDpsBitW;
		pFail += cr.pFail;
		pWhiteCrit += cr.pWhiteCrit;
		pYellowCrit += cr.pYellowCrit;
		pWhiteCritCap += cr.pWhiteCritCap;
		mSwingTimer += cr.mSwingTimer;
		mOoCPPM += cr.mOoCPPM;
		mOoCReturn += cr.mOoCReturn;
		mEnergyRegenRate += cr.mEnergyRegenRate;

		mNrSavageRoar += cr.mNrSavageRoar;
		mSavageRoarUptime += cr.mSavageRoarUptime;
		mSavageRoarCPs += cr.mSavageRoarCPs;

		mNrMangles += cr.mNrMangles;
		mMangleUptime += cr.mMangleUptime;

		mNrShreds += cr.mNrShreds;

		mNrRakes += cr.mNrRakes;
		mRakeUptime += cr.mRakeUptime;

		mNrRips += cr.mNrRips;
		mRipUptime += cr.mRipUptime;

		mNrBites += cr.mNrBites;
		mNrBitesBase += cr.mNrBitesBase;
		mNrBitesPerRip += cr.mNrBitesPerRip;
		mNrBitesBitW += cr.mNrBitesBitW;
		pBiteCrit += cr.pBiteCrit;

		mNrTigersFurys += cr.mNrTigersFurys;
		mTigersFuryUptime += cr.mTigersFuryUptime;

		mNrBerserks += cr.mNrBerserks;
		mBerserkUptime += cr.mBerserkUptime;

		mSegmentTime += cr.mSegmentTime;

		mArmorValue += cr.mArmorValue;
		mArmorMitigation += cr.mArmorMitigation;

		pWhiteDamage += cr.pWhiteDamage;
		pFurySwipesDamage += cr.pFurySwipesDamage;
		pRavageDamage += cr.pRavageDamage;
		pMangleDamage += cr.pMangleDamage;
		pShredDamage += cr.pShredDamage;
		pRakeDamage += cr.pRakeDamage;
		pRipDamage += cr.pRipDamage;
		pBiteDamage += cr.pBiteDamage;
		pOtherDamage += cr.pOtherDamage;

		mAvgWhiteAttack += cr.mAvgWhiteAttack;
		mAvgRavageAttack += cr.mAvgRavageAttack;
		mMangleDPE += cr.mMangleDPE;
		mShredDPE += cr.mShredDPE;
		mRakeDPE += cr.mRakeDPE;
		mRipDPE += cr.mRipDPE;
		mBiteDPE += cr.mBiteDPE;

		if (mProcUptimes == null) {
			// XXX: Should I copy this?
			mProcUptimes = cr.mProcUptimes;
		} else {
			if (cr.mProcUptimes != null && cr.mProcUptimes.isEmpty() == false) {
				for (Map.Entry<ProcEffect, Double> entry : cr.mProcUptimes.entrySet()) {
					ProcEffect proc = entry.getKey();
					Double newUptime = entry.getValue();
					Double oldUptime = mProcUptimes.get(proc);

					mProcUptimes.put(proc, (oldUptime + newUptime));
				}
			}
		}

		return this;
	}

	private String round(double x) {
		// TODO: Hard-coded to 5 for now, need to get it from.. CatModel?
		DecimalFormat df = new DecimalFormat("#.#####");
		return df.format(x);
	}

	public String toString() {
		StringBuilder tmp = new StringBuilder();

		double error = getStdError();
		double stdDev = getStdDev();
		if (error > 0) {
			// XXX: This is kind of ugly.
			tmp.append("DPS: ").append(round(getPerformance())).append(" +/- ").append(round(error * 1.96)).append('\n');
			tmp.append("StdDev: ").append(round(stdDev)).append('\n');
		} else {
			tmp.append("DPS: ").append(round(getPerformance())).append('\n');
		}

		// XXX: These should probably also display error bound.
		if (mDpsBase > 0 && mDpsBitW > 0) {
			tmp.append("DPS (> 25%): ").append(round(mDpsBase / mIterations)).append('\n');
			tmp.append("DPS (< 25%): ").append(round(mDpsBitW / mIterations)).append('\n');
		}

		tmp.append("Chance Miss/Dodge (%): ").append(round(pFail / mIterations * 100)).append('\n');
		tmp.append("Crit % for White: ").append(round(pWhiteCrit / mIterations * 100)).append('\n');
		tmp.append("Crit % for Yellow: ").append(round(pYellowCrit / mIterations * 100)).append('\n');
		tmp.append("Crit Cap % for White: ").append(round(pWhiteCritCap / mIterations * 100)).append('\n');
		tmp.append("Attack Speed: ").append(round(mSwingTimer / mIterations)).append('\n');

		tmp.append("OoC Proc Rate (PPM): ").append(round(mOoCPPM / mIterations)).append('\n');
		tmp.append("OoC Proc Return: ").append(round(mOoCReturn / mIterations)).append('\n');
		tmp.append("Energy Regen (e/s): ").append(round(mEnergyRegenRate / mIterations)).append('\n');

		if (mNrMangles > 0) {
			tmp.append("Number Mangles: ").append(round(mNrMangles / mIterations)).append('\n');
		}
		tmp.append("Mangle Uptime (%): ").append(round(mMangleUptime / mIterations * 100)).append('\n');
		if (mNrShreds > 0) {
			tmp.append("Number Shreds: ").append(round(mNrShreds / mIterations)).append('\n');
		}
		if (mNrRakes > 0) {
			tmp.append("Number Rakes: ").append(round(mNrRakes / mIterations)).append('\n');
			tmp.append("Rake Uptime (%): ").append(round(mRakeUptime / mIterations * 100)).append('\n');
		}
		if (mNrRips > 0) {
			tmp.append("Number Rips: ").append(round(mNrRips / mIterations)).append('\n');
			tmp.append("Rip Uptime (%): ").append(round(mRipUptime / mIterations * 100)).append('\n');
		}
		if (mNrSavageRoar > 0) {
			tmp.append("Number Savage Roars: ").append(round(mNrSavageRoar / mIterations)).append('\n');
			tmp.append("Savage Roar Uptime (%): ").append(round(mSavageRoarUptime / mIterations * 100)).append('\n');
			tmp.append("Savage Roar CP(s): ").append(round(mSavageRoarCPs / mIterations)).append('\n');
		}
		if (mNrBites > 0) {
			tmp.append("Number Bites: ").append(round(mNrBites / mIterations)).append('\n');
	    	if (mNrBitesBase > 0 && mNrBitesBitW > 0) {
				tmp.append("Number Bites (> 25%) : ").append(round(mNrBitesBase / mIterations)).append('\n');
				tmp.append("Bites per Rip (> 25%) : ").append(round(mNrBitesPerRip / mIterations)).append('\n');
				tmp.append("Number Bites (< 25%) : ").append(round(mNrBitesBitW / mIterations)).append('\n');
	    	} else {
	    		tmp.append("Bites per Rip : ").append(round(mNrBitesPerRip / mIterations)).append('\n');
	    	}
			tmp.append("Crit % for Bite: ").append(round(pBiteCrit / mIterations * 100)).append('\n');
		}
		if (mNrTigersFurys > 0) {
			tmp.append("Number Tiger's Furys: ").append(round(mNrTigersFurys / mIterations)).append('\n');
			tmp.append("Tiger's Fury Uptime (%): ").append(round(mTigersFuryUptime / mIterations * 100)).append('\n');
		}
		if (mNrBerserks > 0) {
			tmp.append("Number Berserks: ").append(round(mNrBerserks / mIterations)).append('\n');
			tmp.append("Berserk Uptime (%): ").append(round(mBerserkUptime / mIterations * 100)).append('\n');
		}

		tmp.append("Segment Time (s): ").append(round(mSegmentTime / mIterations)).append('\n');

		tmp.append("Averaged Armor Value: ").append(round(mArmorValue / mIterations)).append('\n');
		tmp.append("Armor Mitigation (%): ").append(round(mArmorMitigation / mIterations * 100)).append('\n');

		// TODO: I should separate this stuff out between Feast of Flesh and not.
		// FIXME: Simulator results the way I'm accumulating will not add to 100%.  (Does this matter?)
		tmp.append("White Damage (%) : ").append(round(pWhiteDamage / mIterations * 100)).append('\n');
		if (pFurySwipesDamage > 0) tmp.append("Fury Swipes Damage (%): ").append(round(pFurySwipesDamage / mIterations * 100)).append('\n');
		if (pRavageDamage > 0) tmp.append("Ravage Damage (%): ").append(round(pRavageDamage / mIterations * 100)).append('\n');
		if (pMangleDamage > 0) tmp.append("Mangle Damage (%): ").append(round(pMangleDamage / mIterations * 100)).append('\n');
		if (pShredDamage > 0) tmp.append("Shred Damage (%): ").append(round(pShredDamage / mIterations * 100)).append('\n');
		if (pRakeDamage > 0) tmp.append("Rake Damage (%): ").append(round(pRakeDamage / mIterations * 100)).append('\n');
		if (pRipDamage > 0) tmp.append("Rip Damage (%): ").append(round(pRipDamage / mIterations * 100)).append('\n');
		if (pBiteDamage > 0) tmp.append("Bite Damage (%): ").append(round(pBiteDamage / mIterations *  100)).append('\n');
		if (pOtherDamage > 0) tmp.append("Other Damage (%): ").append(round(pOtherDamage / mIterations *  100)).append('\n');

		// Note: Ravage shows an average damage value because it's only a good idea to use it when Stampede is up.  (Free, DPE is infinite.)
		tmp.append("Avg. White Attack: ").append(round(mAvgWhiteAttack / mIterations)).append('\n');
		if (mAvgRavageAttack > 0) tmp.append("Avg. Ravage Attack: ").append(round(mAvgRavageAttack / mIterations)).append('\n');
		if (mMangleDPE > 0) tmp.append("Mangle DPE: ").append(round(mMangleDPE / mIterations)).append('\n');
		if (mShredDPE > 0) tmp.append("Shred DPE: ").append(round(mShredDPE / mIterations)).append('\n');
		if (mRakeDPE > 0) tmp.append("Rake DPE: ").append(round(mRakeDPE / mIterations)).append('\n');
		if (mRipDPE > 0) tmp.append("Rip DPE: ").append(round(mRipDPE / mIterations)).append('\n');
		if (mBiteDPE > 0) tmp.append("Bite DPE: ").append(round(mBiteDPE / mIterations)).append('\n');

		if (mProcUptimes != null && mProcUptimes.isEmpty() == false) {
			for (Map.Entry<ProcEffect, Double> entry : mProcUptimes.entrySet()) {
				ProcEffect proc = entry.getKey();
				Double uptime = entry.getValue();

				tmp.append(proc.name).append(" Uptime (%): ").append(round(uptime / mIterations * 100)).append('\n');
			}
		}

		return tmp.toString();
	}
}
